package com.wyg.learn_javassist;

import javassist.*;
import javassist.util.proxy.ProxyFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * @description:
 * @author: WYG
 * @time: 2021/4/8 19:34
 */

public class TestJavassist {

    public static void main(String[] args) throws Exception {
        UserService userService = perfectCreateProxy(UserService.class, new InvocationHandler() {
            @Override
            public Object invoke(String methodName, Object[] args) {
                if (methodName.equals("sayHello")) {
                    System.out.println("hello:\t" + args[0]);
                } else if (methodName.equals("addUser")) {
                    System.out.println("添加user" + args[0] + "成功!");
                }
                return null;
            }
        });
        userService.sayHello("wyg");
        userService.addUser("wyg");

        JdkProxy();
    }

    public static UserService createProxy() throws Exception {
        ClassPool classPool = new ClassPool();
        // 设置classloader
        classPool.appendSystemPath();
        // javassist --> ASM(编辑jvm指令码)
        // 1、创建一个类
        CtClass userServiceImpl = classPool.makeClass("UserServiceImpl");
        userServiceImpl.addInterface(classPool.get(UserService.class.getName()));
        // 2、创建一个方法
        CtMethod sayHello = CtNewMethod.make(CtClass.voidType,                              // 方法的返回类型
                "sayHello",                                     // 方法名称
                new CtClass[]{classPool.get(String.class.getName())},   // 方法的参数类型
                new CtClass[0],                                         // 方法抛出的异常
                "System.out.println(\"hello:\" + $1);",           // 方法的具体实现
                userServiceImpl);//  将该方法加入到对应的class中
        userServiceImpl.addMethod(sayHello);
        // 3、实例化一个对象
        Class aClass = classPool.toClass(userServiceImpl);
        return (UserService) aClass.getDeclaredConstructor().newInstance();
    }

    // 1.支持所有接口代理
    // 2.按常规方式传递实现
    static int count = 0;

    /**
     *
     * @param calInterface  接口类
     * @param invocationHandler 调用方法的辅助类
     * @param <T>   返回类型
     * @return
     * @throws Exception
     */
    public static <T> T perfectCreateProxy(Class<T> calInterface, InvocationHandler invocationHandler) throws Exception {
        // 1.创建一个类
        ClassPool classPool = new ClassPool();
        classPool.appendSystemPath();
        CtClass impl = classPool.makeClass("$proxy" + count++);
        impl.addInterface(classPool.get(calInterface.getName()));
        // 2.添加属性handler对象
        CtField field = CtField.
                make("public com.wyg.learn_javassist.TestJavassist.InvocationHandler handler = null;", impl);
        impl.addField(field);
        // 传递方法名称和参数，s为方法名，args为参数
        String src = "return ($r)this.handler.invoke(\"%s\", $args);";
        String voidSrc = "this.handler.invoke(\"%s\", $args);";
        // 3.创建这个接口下的所有方法实现
        for (Method method : calInterface.getMethods()) {
            CtClass returnType = classPool.get(method.getReturnType().getName());
            String name = method.getName();
            CtClass[] parameters = toCtClass(classPool, method.getParameterTypes());
            CtClass[] errors = toCtClass(classPool, method.getExceptionTypes());
            String srcImpl = method.getReturnType().equals(Void.class) ? voidSrc : src;
            CtMethod newMethod = CtNewMethod.make(returnType, name, parameters, errors, String.format(srcImpl, name), impl);
            impl.addMethod(newMethod);
        }
        // 生成字节码
        byte[] bytes = impl.toBytecode();
        // 类加载到当前的ClassLoader中
        Class aClass = classPool.toClass(impl);
        Object o = aClass.getDeclaredConstructor().newInstance();
        aClass.getField("handler").set(o, invocationHandler);
        return (T) o;
    }

    private static CtClass[] toCtClass(ClassPool classPool, Class[] classes) {
        return Arrays.stream(classes).map(c -> {
            try {
                return classPool.get(c.getName());
            } catch (NotFoundException e) {
                throw new RuntimeException();
            }
        }).collect(Collectors.toList()).toArray(new CtClass[0]);
    }

    public interface InvocationHandler {
        Object invoke(String methodName, Object args[]);
    }
    public class InvocationHandlerImpl implements InvocationHandler {
        @Override
        public Object invoke(String methodName, Object[] args) {
            System.out.println("test");
            return null;
        }
    }
    public interface UserService {
        void sayHello(String name);
        void addUser(String name);
    }


    public static void JdkProxy() {
        ClassLoader classLoader = ProxyFactory.class.getClassLoader();
        Class<?>[] interfaces = new Class[]{ UserService.class };
        java.lang.reflect.InvocationHandler handler = ((proxy, method, args) -> {
            System.out.println("hello wyg");
            return null;
        });
        UserService service = (UserService) Proxy.newProxyInstance(classLoader, interfaces, handler);
        service.sayHello("wyg");
    }
}
